package io.hepu.robotize.aop;

import io.hepu.robotize.IContext;
import io.hepu.robotize.base.BaseEntity;
import io.hepu.robotize.util.JCollection;
import io.hepu.robotize.util.JDate;
import io.hepu.robotize.util.Reflects;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Objects;

@Aspect
@Component
public class PretreatAspect {
    private static final Logger logger = LoggerFactory.getLogger(PretreatAspect.class);

    @Pointcut("@annotation(io.hepu.robotize.aop.PreCreate)")
    public void createPointcut() {
    }

    @Pointcut("@annotation(io.hepu.robotize.aop.PreUpdate)")
    public void updatePointcut() {
    }

    @Before("createPointcut()")
    public void beforeCreate(JoinPoint joinPoint) throws Throwable {
        // Do pretreatment before insert to database follow.
        processEntities(joinPoint, this::preCreate);
    }

    @Before("updatePointcut()")
    public void beforeUpdate(JoinPoint joinPoint) throws Throwable {
        // Do pretreatment before update to database follow.
        processEntities(joinPoint, this::preUpdate);
    }

    private void processEntities(JoinPoint joinPoint, EntityOperation operation) {
        Object arg = joinPoint.getArgs()[0];
        if (arg instanceof List<?>) {
            List<?> entities = JCollection.asList(arg, BaseEntity.class);
            entities.forEach(operation::apply);
        } else {
            operation.apply(arg);
        }
    }

    @FunctionalInterface
    private interface EntityOperation {
        void apply(Object entity);
    }

    /**
     * Handle entity before insert into database
     *
     * @param vo entity to handle
     */
    private void preCreate(Object vo) {
        Objects.requireNonNull(vo, "Entity cannot be NULL for INSERT");
        Reflects.orSet(vo, "creator", IContext.getOperatorId());
        Reflects.orSet(vo, "updater", IContext.getOperatorId());
        Reflects.set(vo, "createAt", JDate.now());
        Reflects.set(vo, "updateAt", JDate.now());
        logger.debug("PreCreate processing before inserting entity: {}", vo);
    }

    /**
     * Handle entity before update into database
     *
     * @param vo entity to handle
     */
    private void preUpdate(Object vo) {
        Objects.requireNonNull(vo, "Entity cannot be NULL for UPDATE");
        Reflects.set(vo, "updater", IContext.getOperatorId());
        Reflects.set(vo, "updateAt", JDate.now());
        logger.debug("PreUpdate processing before updating entity: {}", vo);
    }
}
